use serde::Serialize;
use tokio::fs;

use crate::{
    config::ADV_DIR,
    error::{ec, new_err_msg, AppErr, IoErr, SqlxErr},
    utils::current_timestamp,
};

use super::{adv_cfg, get_pool, Conn};

async fn write_file(id: u64, body: &[u8]) -> Result<(), IoErr> {
    fs::write(format!("{}/{}", ADV_DIR, id), body).await?;
    Ok(())
}

async fn delete_file(id: u64) -> Result<(), IoErr> {
    fs::remove_file(format!("{}/{}", ADV_DIR, id)).await?;
    Ok(())
}

async fn read_file(id: u64) -> Result<Box<[u8]>, IoErr> {
    let data = fs::read(format!("{}/{}", ADV_DIR, id)).await?;
    Ok(data.into_boxed_slice())
}

pub async fn is_exists(conn: &mut Conn, id: u64) -> Result<bool, SqlxErr> {
    let ret = sqlx::query!("SELECT id FROM tb_adv WHERE id = ?", id)
        .fetch_optional(conn)
        .await?;
    Ok(ret.is_some())
}

pub async fn create(adv_type: u8, name: &str, body: &[u8]) -> Result<u64, AppErr> {
    let mut tx = get_pool().begin().await?;

    let now = current_timestamp();
    let ret = sqlx::query!(
        r#"
        INSERT INTO tb_adv (adv_type, size, name, create_timestamp) 
        VALUES (?, ?, ?, ?)
    "#,
        adv_type,
        body.len() as u32,
        name,
        now
    )
    .execute(&mut *tx)
    .await;

    match ret {
        Ok(id) => {
            let id = id.last_insert_id();
            write_file(id, body).await?;
            tx.commit().await?;
            Ok(id)
        }

        Err(SqlxErr::Database(err)) if err.is_unique_violation() => {
            Err(new_err_msg(ec::ADV_IS_EXISTS, ec::ADV_IS_EXISTS_MSG))
        }

        Err(e) => Err(e.into()),
    }
}

#[derive(Debug, Serialize)]
pub struct TbAdv {
    id: u64,
    adv_type: u8,
    name: String,
    create_timestamp: u64,
}

pub async fn query_all() -> Result<Box<[TbAdv]>, SqlxErr> {
    let advs = sqlx::query_as!(
        TbAdv,
        "SELECT id, adv_type, name, create_timestamp FROM tb_adv ORDER BY create_timestamp DESC"
    )
    .fetch_all(get_pool())
    .await?;
    Ok(advs.into_boxed_slice())
}

pub async fn delete(id: u64) -> Result<(), SqlxErr> {
    let mut tx = get_pool().begin().await?;
    sqlx::query!("DELETE FROM tb_adv WHERE id = ?", id)
        .execute(&mut *tx)
        .await?;
    adv_cfg::delete_for_adv(&mut tx, id).await?;
    delete_file(id).await?;
    tx.commit().await?;
    Ok(())
}

pub struct AdvFile {
    pub name: String,
    pub body: Box<[u8]>,
}

pub async fn download(id: u64) -> Result<AdvFile, AppErr> {
    let ret = sqlx::query!("SELECT name FROM tb_adv WHERE id = ?", id)
        .fetch_optional(get_pool())
        .await?
        .ok_or(new_err_msg(ec::ADV_NOT_EXISTS, ec::ADV_NOT_EXISTS_MSG))?;

    let data = read_file(id).await?;

    Ok(AdvFile {
        name: ret.name,
        body: data,
    })
}
